using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Flood.Extensions;
using Flood.Log;

namespace Flood
{
	public sealed class Grid
	{
		private readonly ChangeList changeList;
		private readonly Square[,] squares;

		public int Columns { get; private set; }
		public int Rows { get; private set; }

		private Grid(int rows, int columns)
		{
			Rows = rows;
			Columns = columns;
			squares = new Square[rows,columns];
			changeList = new ChangeList();
		}

		public Grid(string encodedPuzzle, int rows, int columns)
			: this(rows, columns)
		{
			Initialize(encodedPuzzle);
			changeList.Clear();
		}

		public Square this[int row, int column]
		{
			get { return squares[row, column]; }
			set { squares[row, column] = value; }
		}

		public void Checkpoint()
		{
			changeList.CheckPoint();
		}

		public void DisableCachingOnTouched()
		{
			changeList.DisableCachingOnTouched();
		}

		public void EnableCachingOnTouched()
		{
			changeList.EnableCachingOnTouched();
		}

		public string Encode()
		{
			StringBuilder sb = new StringBuilder();
			foreach (Square square in squares)
			{
				sb.Append(square.Shape.Color);
			}
			return sb.ToString();
		}

		public override string ToString()
		{
			Dictionary<int, Shape> shapes = new Dictionary<int, Shape>();
			StringBuilder sb = new StringBuilder();
			for (int row = 0; row < Rows; row++)
			{
				for (int column = 0; column < Columns; column++)
				{
					Square square = squares[row,
					                        column];
					sb.Append(' ');
					switch (square.Shape.Status)
					{
						case Status.Dry:
							sb.Append(' ');
							break;
						case Status.Moist:
							sb.Append('<');
							break;
						case Status.Flooded:
							sb.Append('_');
							break;
						default:
							throw new ArgumentOutOfRangeException();
					}
					if (!shapes.ContainsKey(square.Shape.Id))
					{
						shapes.Add(square.Shape.Id,
						           square.Shape);
					}
					sb.Append(square.Shape.Color);
					switch (square.Shape.Status)
					{
						case Status.Dry:
							sb.Append(' ');
							break;
						case Status.Moist:
							sb.Append('>');
							break;
						case Status.Flooded:
							sb.Append('_');
							break;
						default:
							throw new ArgumentOutOfRangeException();
					}
				}
				sb.Append(Environment.NewLine);
			}

			sb.AppendLine("shapes:");
			Dictionary<int, int> squareToShape = new Dictionary<int, int>();
			foreach (KeyValuePair<int, Shape> keyValuePair in shapes.OrderBy(x => x.Key))
			{
				sb.AppendLine(keyValuePair.Value.ToString());
				foreach (Square square in keyValuePair.Value.Parts)
				{
					if (squareToShape.ContainsKey(square.Id))
					{
						if (squareToShape[square.Id] != keyValuePair.Value.Id)
						{
							Console.Error.WriteLine("square " + square + " cannot be in multiple shapes:" + keyValuePair.Value + " and " +
							                  shapes[squareToShape[square.Id]]);
							Environment.Exit(1);
						}
					}
					squareToShape.Add(square.Id,
					                  keyValuePair.Value.Id);
				}
			}
			shapes.OrderBy(x => x.Key).ForEach(x => sb.AppendLine(x.Value.ToString()));
			return sb.ToString();
		}

		public void Undo()
		{
			changeList.Undo();
		}

		private void Initialize(string encodedPuzzle)
		{
			Square edge = new Square(GameConstants.OutOfBoundsId,
			                         GameConstants.OutOfBoundsColor,
			                         Columns,
			                         changeList);
			for (int row = 0; row < Rows; row++)
			{
				for (int column = 0; column < Columns; column++)
				{
					int offset = row * Columns + column;
					Square square = new Square(offset,
					                           encodedPuzzle[offset],
					                           Columns,
					                           changeList);
					squares[row,
					        column] = square;
					SetSquareNeighbors(row,
					                   column,
					                   edge);
				}
			}
		}

		private void SetSquareNeighbors(int row, int column, Square edge)
		{
			Square square = squares[row,
			                        column];
			if (column == 0)
			{
				square.West = edge;
			}
			else
			{
				if (column == Columns - 1)
				{
					square.East = edge;
				}
				Square westSquare = squares[row,
				                            column - 1];
				square.West = westSquare;
				westSquare.East = square;
				if (westSquare.Shape.Color == square.Shape.Color)
				{
					westSquare.Shape.Combine(square.Shape);
				}
			}
			if (row == 0)
			{
				square.North = edge;
			}
			else
			{
				if (row == Rows - 1)
				{
					square.South = edge;
				}
				Square northSquare = squares[row - 1,
				                             column];
				square.North = northSquare;
				northSquare.South = square;
				if (northSquare.Shape.Color == square.Shape.Color &&
				    northSquare.Shape.Id != square.Shape.Id)
				{
					northSquare.Shape.Combine(square.Shape);
				}
			}
		}
	}
}